#include "Applejack/world.hpp"
#include "Applejack/body.hpp"
#include <algorithm>
#include <iostream>

using namespace Shiny;
using namespace Applejack;
using namespace sf;

World::World(b2AABB worldBox, b2Vec2 gravity, unsigned int ratio) : b2World(gravity), _ratio(ratio)
{
  SetContactListener(&_collisionManager);
}

World::~World()
{
  b2Body*          b2body = GetBodyList();

  while (b2body)
  {
    Body* body = reinterpret_cast<Body*>(b2body->GetUserData());

    if (body)
      body->DeleteLater();
    b2body = b2body->GetNext();
  }
  DeleteBodies();
  if (_sounds) delete _sounds;
  if (_hud)    delete _hud;
  if (_script) delete _script;
  if (_anims)  delete _anims;
  if (_tex)    delete _tex;
}

Widgets::Container* World::CreateView(void)
{
  return (new Applejack::View(*this));
}

void World::Update(void)
{
  b2Body*          b2body = GetBodyList();

  DeleteRaycastObservers();
  Step(1.f / 60.f, 6, 2);
  for_each(_raycastObservers.begin(), _raycastObservers.end(), RayCastObserver::TriggerIterate);
  if (_script)
    _script->Run();
  while (b2body)
  {
    Body* body = reinterpret_cast<Body*>(b2body->GetUserData());

    if (body)
    {
      body->UpdateLogic();
      body->UpdateGraphics();
    }
    b2body = b2body->GetNext();
  }
  DeleteBodies();
}

Body* World::GetBody(const std::string& name)
{
  b2Body* b2body = GetBodyList();
  
  while (b2body)
  {
    Body* body = reinterpret_cast<Body*>(b2body->GetUserData());

    if (*body == name)
      return (body);
    b2body = b2body->GetNext();
  }
  return (0);
}

void World::DeleteBody(Body* body)
{
    _toDeleteBodies.push_back(body);
}

void World::DeleteBodies()
{
    std::list<Body*>::iterator it  = _toDeleteBodies.begin();
    std::list<Body*>::iterator end = _toDeleteBodies.end();

    while (it != end)
    {
	delete *it;
	it = _toDeleteBodies.erase(it);
    }
}

void World::DeleteRaycastObservers()
{
    std::list<RayCastObserver*>::iterator it  = _toDeleteRaycastObservers.begin();
    std::list<RayCastObserver*>::iterator end = _toDeleteRaycastObservers.end();

    while (it != end)
    {
	std::list<RayCastObserver*>::iterator obsIt  = _raycastObservers.begin();
	std::list<RayCastObserver*>::iterator obsEnd = _raycastObservers.end();

	while (obsIt != obsEnd)
	{
	    if (*obsIt == *it)
	    {
		delete *obsIt;
		_raycastObservers.erase(obsIt);
		break ;
	    }
	}
	it = _toDeleteRaycastObservers.erase(it);
    }
}

/*
 * Factory
 */
const char* fieldsWorld[] =
  { "size_x", "size_y", "gravity_x", "gravity_y", "scale",
    "textures", "entities", "landscape", 0 };

World::Factory::GenerateFunction World::Factory::_generateFunctions[] =
{
  GenerateResources, GenerateEntities, GenerateHud, GenerateLandscape, GenerateScript, 0
};

void World::Factory::GenerateEntities(const std::string& levelPath, World* world, Data level)
{
  Data      fileEntities   = level["entities"];
  DataTree* entities;

  if (!(fileEntities.Nil()) && (entities = DataTree::Factory::Yaml(levelPath + fileEntities.Value())))
  {
    Data::iterator current = Data(entities).begin();
    Data::iterator end     = Data(entities).end();

    for (; current != end ; ++current)
    {
      Body* body = Body::Factory::New(*world, *current);

      body->SetName((*current).Key());
      std::cout << "Created entity " << (*current).Key() << std::endl;
    }
    delete entities;
  }
}

void World::Factory::GenerateScript(const std::string& levelPath, World* world, Data level)
{
  Data    script       = level["script"];

  if (not script.Nil())
  {
    Data scriptSource = script["source"];
    Data scriptType   = script["type"];

    if ((!scriptSource.Nil()) && (!scriptType.Nil()))
    {
      if (scriptType.Value() == "cpp")
      {
        const Utils::LoadedLib* scriptFile = world->_cppList[scriptSource.Value()];
        Module*                (*scriptNew)(World&);

        if (!scriptFile)
          scriptFile = world->_cppList.LoadLib(scriptSource.Value(), "./scripts/" + scriptSource.Value() + LIB_EXTENSION);
	if (scriptFile)
	{
	    scriptNew      = scriptFile->GetFunction<Module*  (*)(World&)>("New");
	    world->_script = scriptNew(*world);
	}
      }
    }
  }
}

void World::Factory::GenerateHud(const std::string& levelPath, World* world, Data level)
{
  Data    hud            = level["hud"];

  if (not hud.Nil())
  {
    DataTree* fileHud = DataTree::Factory::Yaml(levelPath + hud.Value());

    if (fileHud)
    {
      world->_hud       = new Widgets::Ui(fileHud, &(world->GetTextures()));
      delete fileHud;
    }
  }
}

void World::Factory::GenerateLandscape(const std::string& levelPath, World* world, Data level)
{
  Data    landscape      = level["landscape"];

  if (not landscape.Nil())
    world->_landscape = world->GetTextures()[landscape.Value()];
  else
    world->_landscape = 0;
}

template<typename T>
static void LoadRessource(const std::string& levelPath, T** attribute, Data fieldFile)
{
  DataTree* file;

  if ((!fieldFile.Nil()) && (file = DataTree::Factory::Yaml(levelPath + fieldFile.Value())))
  {
    (*attribute) = new T(file);
    delete file;
  }
  else
    (*attribute) = new T();
}

void World::Factory::GenerateResources(const std::string& levelPath, World* world, Data level)
{
  Data           fileAnimations = level["animations"];
  Data           fileBodies     = level["bodies"];
  DataTree*      animations     = 0;
  DataTree*      bodies         = 0;

  LoadRessource(levelPath, &(world->_tex),    level["textures"]);
  LoadRessource(levelPath, &(world->_sounds), level["sounds"]);
  if ((!fileAnimations.Nil()) && (animations  = DataTree::Factory::Yaml(levelPath + fileAnimations.Value())))
  {
    world->_anims  = new AnimationList(animations, world->GetTextures());
    delete animations;
  }
  else
  {
    std::cout << "/!\\ Warning: No animation file" << std::endl;
    world->_anims  = new AnimationList;
  }
  if ((!fileBodies.Nil())     && (bodies      = DataTree::Factory::Yaml(levelPath + fileBodies.Value())))
  {
    world->_objects.Load(*world, bodies);
    delete bodies;
  }
}

Level* World::Factory::New(const std::string& levelPath, Data level)
{
  if (level.Nil())
    return (0); // Incorrect file level
  World*         world;
  b2AABB         worldBox;
  b2Vec2         worldGravity;
  sf::Vector2f   size;
  sf::Vector2f   gravity;
  unsigned int   ratio;

  size.x          = level["size_x"];
  size.y          = level["size_y"];
  gravity.x       = level["gravity_x"];
  gravity.y       = level["gravity_y"];
  ratio           = level["scale"];
  worldBox.lowerBound.Set(-size.x / ratio, -size.y / ratio);
  worldBox.upperBound.Set(size.x  / ratio, size.x  / ratio);
  worldGravity.x  = gravity.x / ratio;
  worldGravity.y  = gravity.y / ratio;
  world           = new World(worldBox, worldGravity, ratio);
  world->SetEventManager(EventManager::Get());
  world->_size    = size;

  for (char i = 0 ; Factory::_generateFunctions[i] != 0 ; ++i)
    Factory::_generateFunctions[i](levelPath, world, level);
  return (world);
}

/* RayCastObserver */
bool World::RayCastCollision::operator<(const RayCastCollision& right) const
{
    return (fraction < right.fraction);
}

World::RayCastObserver::RayCastObserver(World& world, sf::Vector2f point1, sf::Vector2f point2, Behaviour b) : _world(world)
{
    b2Vec2 p1(point1.x, point2.y);
    b2Vec2 p2(point2.x, point2.y);

    _behaviour = b;
    world.AddRayCast(this);
    world.RayCast(this, p1, p2);
}

float32 World::RayCastObserver::ReportFixture(b2Fixture *fixture, const b2Vec2 &point, const b2Vec2 &normal, float32 fraction)
{
    Fixture*         applejackFixture = reinterpret_cast<Fixture*>(fixture->GetUserData());

    std::cout << "ReportFixture called" << std::endl;
    if (applejackFixture)
    {
	RayCastCollision collision;

	collision.fixture  = applejackFixture;
	collision.point    = sf::Vector2f(point.x, point.y);
	collision.normal   = sf::Vector2f(normal.x, normal.y);
	collision.fraction = fraction;
	_collisions.push_back(collision);
    }
    switch (_behaviour)
    {
    case Closest:
	return (fraction);
    case Collide:
	return (0);
    case All:
    case AllSorted:
	break ;
    }
    return (1);
}

void World::RayCastObserver::Trigger(void)
{
    switch (_behaviour)
    {
    case Closest:
	while (_collisions.size() > 1)
	  _collisions.erase(_collisions.begin());
	break ;
    case AllSorted:
	std::sort(_collisions.begin(), _collisions.end());
	break ;
    case All:
    case Collide:
	break ;
    }
    Emit(_collisions);
    _collisions.clear();
}

void World::RayCastObserver::TriggerIterate(RayCastObserver* it)
{
    it->Trigger();
}
